PROJECTENGRAM

[PS] CHARIOT ARCANA

Chariot Arcana: Activity Selection & Fractional Knapsack

Tags: #◇CSALGCM #◇Problem Sets #◇HackerRank #◇Algorithms #◇Greedy Algorithms


◇Overview

The Chariot Arcana emphasizes optimal forward progress without backtracking: The Greedy Paradigm. Solutions make the locally optimal choice at each step to arrive at a globally optimal solution. This problem set explores Activity Selection (Interval Scheduling) and the Fractional Knapsack Problem.


◇Problem 1: Activity Selection (Interval Scheduling)

Problem Description

Given a set of nn activities S={a1,a2,…,an}S = \{a_1, a_2, \dots, a_n\}, where each activity aia_i has a start time sis_i and a finish time fif_i (si<fis_i < f_i). Two activities aia_i and aja_j are compatible if their time intervals do not overlap (si≥fjs_i \ge f_j or sj≥fis_j \ge f_i). Find a maximum-cardinality subset of mutually compatible activities.

Constraints:

  • 1≤n≤1051 \le n \le 10^5
  • 0≤si<fi≤1090 \le s_i < f_i \le 10^9

Greedy Choice Property: The Exchange Argument Proof

Greedy Strategy: Sort all activities in non-decreasing order of their finish times: f1≤f2≤⋯≤fnf_1 \le f_2 \le \dots \le f_n. Always pick the activity with the earliest finish time that is compatible with previously selected activities.

Stepped Proof by Exchange Argument:

  1. Let AkA_k be the activity with the earliest finish time in the current remaining candidate set.
  2. Suppose an optimal solution OPTOPT does not contain AkA_k. Let the first activity in OPTOPT ordered by finish time be AjA_j (j≠kj \ne k).
  3. Since AkA_k has the minimal finish time, fk≤fjf_k \le f_j.
  4. Replacing AjA_j with AkA_k in OPTOPT creates a new subset OPT′=(OPT∖{Aj})∪{Ak}OPT' = (OPT \setminus \{A_j\}) \cup \{A_k\}.
  5. Because fk≤fjf_k \le f_j, AkA_k finishes before or at the same time AjA_j did; hence, AkA_k cannot conflict with any subsequent activities in OPTOPT.
  6. Therefore, OPT′OPT' is also a valid compatible subset with ∣OPT′∣=∣OPT∣|OPT'| = |OPT|, proving that the greedy choice AkA_k belongs to at least one optimal solution.

Stepped Trace

Consider n=6n = 6 activities sorted by finish time fif_i:

Activity iiStart sis_iFinish fif_iActionReason
a1a_112SelectFirst activity with earliest finish (last_end=2last\_end = 2)
a2a_234Selects2=3≥last_end=2s_2 = 3 \ge last\_end = 2 (last_end←4last\_end \leftarrow 4)
a3a_306Rejects3=0<last_end=4s_3 = 0 < last\_end = 4 (Overlaps)
a4a_457Selects4=5≥last_end=4s_4 = 5 \ge last\_end = 4 (last_end←7last\_end \leftarrow 7)
a5a_589Selects5=8≥last_end=7s_5 = 8 \ge last\_end = 7 (last_end←9last\_end \leftarrow 9)
a6a_659Rejects6=5<last_end=9s_6 = 5 < last\_end = 9 (Overlaps)

Maximum compatible activities selected: 4 ({a1,a2,a4,a5}\{a_1, a_2, a_4, a_5\}).


◇Problem 2: Fractional Knapsack Problem

Problem Description

Given a knapsack with maximum weight capacity WW, and nn items where each item ii has weight wi>0w_i > 0 and value vi>0v_i > 0. Unlike the 0/1 knapsack, fractions of items may be taken (e.g., xi∈[0,1]x_i \in [0, 1] of item ii, contributing xi⋅wix_i \cdot w_i weight and xi⋅vix_i \cdot v_i value). Maximize total value.

Constraints:

  • 1≤n≤1051 \le n \le 10^5
  • 1≤W≤1091 \le W \le 10^9
  • 1≤wi,vi≤1051 \le w_i, v_i \le 10^5

Approach & Intuition

Compute the value density (value per unit weight) for each item:
ρi=viwi\rho_i = \frac{v_i}{w_i}
Sort items in descending order of density: ρ1≥ρ2≥⋯≥ρn\rho_1 \ge \rho_2 \ge \dots \ge \rho_n.
Greedily consume items in this order: take the full item if remaining capacity permits; otherwise, take whatever fraction fills the remaining capacity and terminate.

Stepped Calculation Trace

Let W=50W = 50, with 3 items:

  • Item 1: v1=60,w1=10  ⟹  ρ1=6.0v_1 = 60, w_1 = 10 \implies \rho_1 = 6.0
  • Item 2: v2=100,w2=20  ⟹  ρ2=5.0v_2 = 100, w_2 = 20 \implies \rho_2 = 5.0
  • Item 3: v3=120,w3=30  ⟹  ρ3=4.0v_3 = 120, w_3 = 30 \implies \rho_3 = 4.0
  1. Item 1: w1=10≤50w_1 = 10 \le 50. Take 100%: x1=1.0x_1 = 1.0. Added value = 60. Remaining W=40W = 40.
  2. Item 2: w2=20≤40w_2 = 20 \le 40. Take 100%: x2=1.0x_2 = 1.0. Added value = 100. Remaining W=20W = 20.
  3. Item 3: w3=30>20w_3 = 30 > 20. Take fraction x3=20/30=23x_3 = 20 / 30 = \frac{2}{3}. Added value = 23×120=80\frac{2}{3} \times 120 = 80. Remaining W=0W = 0.
  4. Total Maximum Value: 60+100+80=240.060 + 100 + 80 = \mathbf{240.0}.

◇Complete C++ Implementation

『』
CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>

using namespace std;

struct Activity {
    int start;
    int finish;
};

struct Item {
    double value;
    double weight;
};

// Problem 1: Activity Selection
int maxActivities(vector<Activity>& acts) {
    // Sort strictly by finish time
    sort(acts.begin(), acts.end(), [](const Activity& a, const Activity& b) {
        return a.finish < b.finish;
    });

    int count = 0;
    int lastFinish = -1;

    for (const auto& act : acts) {
        if (act.start >= lastFinish) {
            count++;
            lastFinish = act.finish;
        }
    }
    return count;
}

// Problem 2: Fractional Knapsack
double fractionalKnapsack(double W, vector<Item>& items) {
    // Sort descending by value-to-weight ratio
    sort(items.begin(), items.end(), [](const Item& a, const Item& b) {
        return (a.value / a.weight) > (b.value / b.weight);
    });

    double totalValue = 0.0;
    double currentWeight = 0.0;

    for (const auto& item : items) {
        if (currentWeight + item.weight <= W) {
            currentWeight += item.weight;
            totalValue += item.value;
        } else {
            double remain = W - currentWeight;
            totalValue += item.value * (remain / item.weight);
            break; // Knapsack is full
        }
    }
    return totalValue;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    // Test Activity Selection
    vector<Activity> activities = {{1, 2}, {3, 4}, {0, 6}, {5, 7}, {8, 9}, {5, 9}};
    cout << "Max Compatible Activities: " << maxActivities(activities) << "\n"; // Output: 4

    // Test Fractional Knapsack
    vector<Item> items = {{60, 10}, {100, 20}, {120, 30}};
    double W = 50;
    cout << fixed << setprecision(2);
    cout << "Max Knapsack Value: " << fractionalKnapsack(W, items) << "\n"; // Output: 240.00

    return 0;
}

◇References & Navigation

[PS] Chariot Arcana | PROJECT ENGRAM